<?php declare(strict_types=1);
/**
 * @author      xianganyall <xianganyall@gmail.com>
 * @copyright   2023-2025 owner
 **/

namespace Srv\Libs\ConfModel;

use JsonSerializable;
use ReflectionClass;
use ReflectionProperty;
use ReflectionException;
use Srv\Libs\Boot;
use Srv\Libs\Common\CommJson;
use Srv\Libs\Common\CommString;
use Srv\Libs\Common\CommType;

abstract class ConfAbstract implements JsonSerializable
{
    /**
     * @return void
     * clear
     */
    final protected function clear():void
    {
        $varsList                       = get_class_vars(static::class);
        if(count($varsList) > 0) foreach($varsList as $name => $val){
            $valType                    = strtolower(gettype($val));
            switch($valType){
                case CommType::TYPE_STRING: {
                    $this->{$name}      = '';
                    break;
                }
                case CommType::TYPE_INTEGER: {
                    $this->{$name}      = 0;
                    break;
                }
                case CommType::TYPE_FLOAT: {
                    $this->{$name}      = 0.0;
                    break;
                }
                case CommType::TYPE_BOOLEAN: {
                    $this->{$name}      = false;
                    break;
                }
                case CommType::TYPE_ARRAY: {
                    $this->{$name}      = [];
                    break;
                }
            }
        }
    }

    /**
     * @param array $confData
     * @return void
     * init
     */
    final protected function init(array $confData):void
    {
        $varsList                       = get_class_vars(static::class);
        if(count($varsList) < 1) return;
        foreach($varsList as $name => $val){
            $valType                    = strtolower(gettype($val));
            switch($valType){
                case CommType::TYPE_STRING: {
                    if(!isset($confData[$name])) continue 2;
                    $this->{$name}      = strval($confData[$name]);
                    break;
                }
                case CommType::TYPE_INTEGER: {
                    if(!isset($confData[$name])) continue 2;
                    $this->{$name}      = intval($confData[$name]);
                    break;
                }
                case CommType::TYPE_FLOAT: {
                    if(!isset($confData[$name])) continue 2;
                    $this->{$name}      = floatval($confData[$name]);
                    break;
                }
                case CommType::TYPE_BOOLEAN: {
                    if(!isset($confData[$name])) continue 2;
                    $this->{$name}      = CommString::getBoolByString(strval($confData[$name]));
                    break;
                }
                case CommType::TYPE_ARRAY: {
                    if(count($confData) > 0){
                        $nameKey        = $name.'.';
                        $nameSize       = strlen($nameKey);
                        foreach($confData as $confKey => $confVal){
                            if(strlen($confKey) > $nameSize && substr($confKey, 0, $nameSize) === $nameKey){
                                $confKey    = substr($confKey, $nameSize);
                                if(strlen($confKey) < 1 || $confKey === CommJson::JSON_DEFAULT_EMPTY_STRING){
                                    $this->{$name}[] = $confVal;
                                }else{
                                    if(is_numeric($confKey)) $confKey = intval($confKey);
                                    $this->{$name}[$confKey] = $confVal;
                                }
                            }else{
                                if($confKey === $name && is_array($confVal)) $this->{$name} = $confVal;
                            }
                        }
                    }
                    break;
                }
            }
        }
    }

    /**
     * @return array
     * jsonSerialize
     */
    final public function jsonSerialize():array
    {
        return $this->getJsonArray($this, [], true);
    }

    /**
     * @param ConfAbstract|null $ConfObject
     * @param array $ignoreList
     * @param bool $isAll
     * @return array
     * getJsonArray
     */
    final public function getJsonArray(?ConfAbstract $ConfObject = null, array $ignoreList = [], bool $isAll = false):array
    {
        $jsonData                   = [];
        try{
            if(is_null($ConfObject)) $ConfObject = $this;
            $ReflectionClass        = new ReflectionClass($ConfObject);
            $ReflectionPropertyList = $ReflectionClass->getProperties(ReflectionProperty::IS_PROTECTED | ReflectionProperty::IS_PUBLIC | ReflectionProperty::IS_PRIVATE);
            if(count($ReflectionPropertyList) < 1) return $jsonData;
            $methodList             = array_column($ReflectionClass->getMethods(ReflectionProperty::IS_PUBLIC), 'name');
            if(count($methodList) < 1) return $jsonData;
        }catch (ReflectionException $ReflectionException){
            return $jsonData;
        }
        foreach($ReflectionPropertyList as $ReflectionProperty){
            $propertyName           = $ReflectionProperty->getName();
            if(in_array($propertyName, $ignoreList, true) || CommString::startsWith($propertyName, '__')) continue;
            $propertyType           = $ReflectionProperty->getType()->getName();
            if($propertyType === 'bool'){
                $methodNameGet      = 'is'.ucfirst($propertyName);
            }else{
                $methodNameGet      = 'get'.ucfirst($propertyName);
            }
            if(!in_array($methodNameGet, $methodList, true)) continue;
            $propertyValue          = @call_user_func_array([$ConfObject, $methodNameGet], []);
            if($propertyValue instanceof ConfAbstract){
                $jsonData[$propertyName]                    = $this->getJsonArray($propertyValue, [], $isAll);
            }else if(is_array($propertyValue)){
                if($isAll || count($propertyValue) > 0){
                    $jsonData[$propertyName]                = [];
                    foreach($propertyValue as $propertyValueItem){
                        if($propertyValueItem instanceof ConfAbstract){
                            $jsonData[$propertyName][]      = $this->getJsonArray($propertyValueItem, [], $isAll);
                        }else{
                            if($isAll || ($propertyValueItem !== null && $propertyValueItem !== 0 && $propertyValueItem !== '' && $propertyValueItem !== false)){
                                $jsonData[$propertyName][]  = $propertyValueItem;
                            }
                        }
                    }
                }
            }else{
                if($isAll || ($propertyValue !== null && $propertyValue !== 0 && $propertyValue !== '' && $propertyValue !== false)){
                    $jsonData[$propertyName]                = $propertyValue;
                }
            }
        }
        return $jsonData;
    }

    /**
     * @param ConfAbstract|null $ConfObject
     * @param array $ignoreList
     * @return array
     * getPropertyList
     */
    final public function getPropertyList(?ConfAbstract $ConfObject = null, array $ignoreList = []):array
    {
        $jsonData                   = [];
        try{
            if(is_null($ConfObject)) $ConfObject = $this;
            $ReflectionClass        = new ReflectionClass($ConfObject);
            $ReflectionPropertyList = $ReflectionClass->getProperties(ReflectionProperty::IS_PROTECTED | ReflectionProperty::IS_PUBLIC | ReflectionProperty::IS_PRIVATE);
            if(count($ReflectionPropertyList) < 1) return $jsonData;
            $methodList             = array_column($ReflectionClass->getMethods(ReflectionProperty::IS_PUBLIC), 'name');
            if(count($methodList) < 1) return $jsonData;
        }catch (ReflectionException $ReflectionException){
            return $jsonData;
        }
        // 解码文件格式的单行注释方式
        $propertyCommentList        = [];
        $className                  = get_class($this);
        $classNameFilePath          = Boot::getClassFilePath($className);
        if(file_exists($classNameFilePath)){
            $classFileList              = @file($classNameFilePath);
            foreach ($classFileList as $classFileLine){
                $classFileLine          = trim($classFileLine);
                if(preg_match('/^(protected|public|private)\s+.*\s+\$(\S+)\s+=\s*\S+\s*\/\/(.*)$/', $classFileLine, $classMatch)){
                    $propertyCommentList[$classMatch[2]] = trim($classMatch[3]);
                }
                if(preg_match('/^(protected|public|private)\s+const\s+(\S+)\s+=\s*\S+\s*\/\/(.*)$/', $classFileLine, $classMatch)){
                    $propertyCommentList[$classMatch[2]] = trim($classMatch[3]);
                }
            }
        }
        foreach($ReflectionPropertyList as $ReflectionProperty){
            $propertyName           = $ReflectionProperty->getName();
            if(in_array($propertyName, $ignoreList, true) || CommString::startsWith($propertyName, '__')) continue;
            $propertyType           = $ReflectionProperty->getType()->getName();
            $propertyComment        = $ReflectionProperty->getDocComment();
            if((!is_string($propertyComment) || strlen($propertyComment) < 1) && isset($propertyCommentList[$propertyName])) $propertyComment = $propertyCommentList[$propertyName];
            if($propertyType === 'bool'){
                $methodNameGet      = 'is'.ucfirst($propertyName);
            }else{
                $methodNameGet      = 'get'.ucfirst($propertyName);
            }
            if(!in_array($methodNameGet, $methodList, true)) continue;
            $propertyValue              = @call_user_func_array([$ConfObject, $methodNameGet], []);
            $jsonData[$propertyName]    = ['value' => $propertyValue, 'type' => $propertyType, 'comment' => $propertyComment];
        }
        return $jsonData;
    }
}